package cs236369.webserver;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Properties;

import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.ProtocolVersion;
import org.apache.http.entity.ContentProducer;
import org.apache.http.entity.EntityTemplate;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpDateGenerator;
import org.apache.http.protocol.ResponseContent;

class RequestHandlerThread extends Thread {
	private final Queue<Request> requestQueue;
	Config config;

	RequestHandlerThread(Queue<Request> _requestQueue, Config _config) {
		this.requestQueue = _requestQueue;
		this.config = _config;
	}

	public void run() {
		// Extract a request object from the request queue.
		while (true) {
			Request request = null;
			synchronized (requestQueue) {
				while (requestQueue.empty()) {
					try {
						requestQueue.wait();
					} catch (InterruptedException e) {
						Log.err("RequestHandlerThread: waiting for request "
								+ e.toString());
					}
				}
				request = requestQueue.dequeue();
				Log.out("RequestHandlerThread: Dequeued request");
				requestQueue.notifyAll();
			}

			/*
			 * Check whether the requested file exists.
			 */
			HttpResponse response = null;
			response = new DefaultHttpResponseFactory().newHttpResponse(
					HttpVersion.HTTP_1_1, HttpStatus.SC_OK,
					new BasicHttpContext(null));
			DefaultHttpServerConnection conn = request.getConnection();
			BasicHttpProcessor proc = new BasicHttpProcessor();
			String serverPath = request.getPath();
			File f = new File(serverPath);
			/*
			 * In case the file doesn't exist, send back a 404 message.
			 */
			if (!f.exists() || !f.canRead()) {
				Log.out("RequestHandlerThread: Handling bad request, sending 404 page");
				RequestHandlerThread.handleBadRequest(response,
						"Page Doesn't Exist", HttpStatus.SC_NOT_FOUND);
			} else {
				/*
				 * In case the requested file exists.
				 */
				if (config.handlerMap.containsKey(request.getExt())) {
					/*
					 * The file is associated with one of the type handlers.
					 */
					Log.out("RequestHandlerThread: Handling specific type request");
					response = handleSpecificTypeRequest(request,
							request.getExt());
				} else {
					/*
					 * The request is for an existing file not destined for the
					 * specific type handlers.
					 */

					if (request.getMethod().equals("POST")) {
						Log.out("RequestHandlerThread: Handling bad request, sending 400 page");
						RequestHandlerThread
								.handleBadRequest(
										response,
										"POST request not handled by specific-type handler",
										HttpStatus.SC_BAD_REQUEST);
					} else {
						Log.out("RequestHandlerThread: Handling normal request");
						response = handleNormalRequest(f);
					}
				}
			}
			/*
			 * Send the response back to the client.
			 */
			response.addHeader("Time", new HttpDateGenerator().getCurrentDate());
			if (!response.containsHeader("Content-Length")) {
				try {
					new ResponseContent().process(response,
							new BasicHttpContext(null));
				} catch (Exception e) {
					Log.err("RequestHandlerThread: ResponseContent "
							+ e.toString());
				}
			}
			if (config.mimeMap.get(request.getExt()) != null) {
				response.setHeader("Content-Type",
						config.mimeMap.get(request.getExt()));
			} else {
				response.setHeader("Content-Type", "text/html");
			}
			response.addHeader("Connection", "Connection has been closed");
			for (Header h : response.getAllHeaders()) {
				Log.details(h.toString());
			}
			try {
				proc.process(response, new BasicHttpContext(null));
			} catch (IOException e) {
				Log.err("RequestHandlerThread: proc (IOException) "
						+ e.toString());
			} catch (HttpException e) {
				Log.err("RequestHandlerThread: proc (HttpException) "
						+ e.toString());
			}
			try {
				conn.sendResponseHeader(response);
			} catch (HttpException e) {
				Log.err("RequestHandlerThread: sendResponseHeader (HttpException) "
						+ e.toString());
			} catch (IOException e) {
				Log.err("RequestHandlerThread: sendResponseHeader (IOException) "
						+ e.toString());
			}
			try {
				conn.sendResponseEntity(response);
			} catch (HttpException e) {
				Log.err("RequestHandlerThread: sendResponseEntity (HttpException) "
						+ e.toString());
			} catch (IOException e) {
				Log.err("RequestHandlerThread: sendResponseEntity (IOException) "
						+ e.toString());
			}
			try {
				conn.flush();
			} catch (IOException e) {
				Log.err("RequestHandlerThread: flush (IOException) "
						+ e.toString());
			}
			try {
				conn.close();
			} catch (IOException e) {
				Log.err("RequestHandlerThread: close (IOException) "
						+ e.toString());
			}
		}
	}

	private HttpResponse handleSpecificTypeRequest(Request request, String ext) {
		String className = config.handlerMap.get(ext).getClassName();
		Properties props = config.handlerMap.get(ext).getProperties();
		TypeHandler typeHandler = null;
		try {
			typeHandler = (TypeHandler) Class.forName(className)
					.getConstructor(Properties.class).newInstance(props);
		} catch (Exception e) {
			Log.err("RequestHandlerThread: TypeHandler creation & instantiation "
					+ e.toString());
		}
		HttpResponse response = new BasicHttpResponse(new BasicStatusLine(
				new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
		Log.out("RequestHandlerThread: Calling specific type handler");
		typeHandler.handle(request, response);
		Log.out("RequestHandlerThread: Finished specific type handling");
		return response;
	}

	private HttpResponse handleNormalRequest(File f) {
		HttpResponse response;
		HttpContext context = new BasicHttpContext(null);
		response = new DefaultHttpResponseFactory().newHttpResponse(
				HttpVersion.HTTP_1_1, HttpStatus.SC_OK, context);
		FileEntity body = new FileEntity(f, "text/html");
		body.setContentType("text/html; charset=UTF-8");
		response.setEntity(body);
		Log.out("RequestHandlerThread: Normal response created");
		return response;
	}

	static void handleBadRequest(HttpResponse response, String h1, int status) {
		final String h = h1;
		EntityTemplate body = new EntityTemplate(new ContentProducer() {
			@Override
			public void writeTo(final OutputStream outstream)
					throws IOException {
				OutputStreamWriter writer = new OutputStreamWriter(outstream,
						"UTF-8");

				writer.write("<html><body><h1>" + h + "</h1>");
				writer.write("So we can't give the page to you, Cya!");
				writer.write("</body></html>");
				writer.flush();
			}
		});
		body.setContentType("text/html; charset=UTF-8");
		response.setStatusCode(status);
		response.setEntity(body);
		Log.out("RequestHandlerThread: Response to bad request created");
	}
}
